﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Adventure.Library;
using Adventure.Server.Data;
using Adventure.Server.Library;

namespace Adventure.Server.Manager
{
    /// <summary>
    /// Manager of all objects inside this Adventure.
    /// </summary>
    public class ObjectManager : IObjectManager
    {
        #region fields

        /// <summary>
        /// Tracesource for all log messages.
        /// </summary>
        private static readonly TraceSource Logger = new TraceSource(ServerConstants.ServerTraceSourceNameApplication);

        /// <summary>
        /// All known Objects.
        /// </summary>
        private readonly Dictionary<string, SavedObject> _knownObjects = new Dictionary<string, SavedObject>();

        /// <summary>
        /// DataAdapter used to load and save objects.
        /// </summary>
        private IDataAdapter _dataAdapter;

        #endregion

        #region Lifetime

        #endregion

        #region Properties

        #endregion

        #region Public Methods

        public void SetDataAdapter(IDataAdapter adapter)
        {
            // verify arguments
            if (adapter == null) throw new ArgumentNullException(nameof(adapter));

            // DataAdapter can only be set when it wasn't set so far or no objects are loaded.
            if (_dataAdapter == null || _knownObjects.Count == 0)
            {
                Logger.TraceEvent(TraceEventType.Verbose, (int)ServerTraceId.Managers, Properties.Resources.SettingDataAdapterInObjectManagerMessage);
                _dataAdapter = adapter;
                return;
            }

            // Throw an exception.
            string message = Properties.Resources.SettingDataAdapterInObjectManagerErrorMessage;
            Logger.TraceEvent(TraceEventType.Error, (int)ServerTraceId.Managers, message);
            throw new AdventureException(message);
        }

        /// <summary>
        /// Create a new object.
        /// </summary>
        /// <param name="attributes">Attributes of the new object.</param>
        /// <returns>The new object or null.</returns>
        public SavedObject CreateObject(ObjectAttributes attributes)
        {
            // Create the new object
            using (var obj = SavedObject.Create(attributes))
            {

                // Check that we got a new object.
                if (obj == null)
                    return null;

                // Check if the object already exists
                if (_knownObjects.ContainsKey(obj.Id) || _dataAdapter.GetObjectAttributes(obj.Id) != null)
                    return null;

                // store the object inside the known objects.
                _knownObjects[obj.Id] = obj;

                // We hand out a reference and mark object as changed.
                obj.IncreaseReferenceCounter();
                obj.IsChanged = true;

                // Return the new object.
                return obj;
            }
        }

        public void Delete(SavedObject obj)
        {
            // Nothing to do if obj is null.
            if (obj == null)
                return;

            // Delete object.
            Delete(obj.Id);
        }

        public void Delete(string id)
        {
            // Nothing to do if id is empty.
            if (string.IsNullOrEmpty(id))
                return;

            // Remove from known objects
            if (_knownObjects.ContainsKey(id))
                _knownObjects.Remove(id);

            // Remove from data source
            _dataAdapter.Delete(id);

        }

        /// <summary>
        /// Gets the object with the given id.
        /// </summary>
        /// <param name="id">Id of the object to get.</param>
        /// <returns>The SavedObject instance or null if not found.</returns>
        public SavedObject GetObject(string id)
        {
            try
            {
                // Check if object is known and return it with increased ReferenceCounter.
                if (_knownObjects.ContainsKey(id))
                {
                    var result = _knownObjects[id];
                    result.IncreaseReferenceCounter();
                    return result;
                }

                // try to load the attributes
                var attributes = _dataAdapter.GetObjectAttributes(id);

                // Return null if attributes couldn't be loaded.
                if (attributes == null)
                    return null;

                // Create the object, store it in the known objects and then return it.
                using (var result = SavedObject.Create(attributes))
                {
                    _knownObjects[result.Id] = result;
                    result.IncreaseReferenceCounter();
                    return result;
                }
            }
            catch (Exception ex)
            {
                Logger.TraceEvent(TraceEventType.Error, (int)ServerTraceId.Managers, Properties.Resources.UnableToGetObjectError, id, ex.Message, ex.StackTrace);
                return null;
            }
        }

        /// <summary>
        /// Saves the object.
        /// </summary>
        /// <param name="obj">Object to save.</param>
        public void Save(SavedObject obj)
        {
            // Verify
            if (obj == null) throw new ArgumentNullException(nameof(obj));
            if (_dataAdapter == null) throw new AdventureException("DataAdapter not set in ObjectManager!");

            // Save the object!
            obj.Save(_dataAdapter);
        }

        #endregion

    }
}
